#!/bin/bash
#
# This script handles all PostgreSQL operations for Oracle Clusterware. Available actions:
# start - Starts specified instance
# stop - Stops instance
# check - Checks if is alive
# clean - Forcefully stops instance
# All other actions start console
#
# Script requires environment variable PGSQL_SID that specified the PostgreSQL instance name
# and instance parameters must be configured in instances.sh file

# Ilmar Kerm, 2012
# http://code.google.com/p/postgresql-with-oracle-clusterware-scripts/


# Checking if PGSQL_SID exists
if [ ! $PGSQL_SID ]; then
    echo "PGSQL_SID must be specified"
    exit 1
fi

# Load cluster instance configuration file
SCRIPTPATH=`readlink -f $0`
SCRIPTDIR=`dirname $SCRIPTPATH`
source $SCRIPTDIR/instances.sh
source $SCRIPTDIR/functions.sh

# Read the correct instance config
# Here it should declare DATADIR BINDADDR and SOFTWARE
TEMP_VAR1="$PGSQL_SID[*]"
TEMP_VAR=${!TEMP_VAR1}
declare -r $TEMP_VAR

# Declare other file locations
HOSTNAME=`hostname`
PIDFILE=$DATADIR/postmaster.pid
SOCKET=$DATADIR/clusterware
LOCKFILE=$LOCKDIR/$PGSQL_SID.lock
SYSLOGTAG=postgres_$PGSQL_SID
exitval=0

# Does DATADIR actually exist and contain valid PostgreSQL data dir
if [ ! -f "$DATADIR/postgresql.conf" ]; then
    echo "$DATADIR does not seem to be a valid PostgreSQL data directory"
    exit 1
fi

# Is SOFTWARE a valid PostgreSQL software installation
if [ ! -f "$SOFTWARE/bin/postgres" ]; then
    echo "$SOFTWARE does not seem to be a valid PostgreSQL installation directory"
    exit 1
fi

# Check if user executing the script is the same as owner of the data directory
userid=`id -u`
if [[ $userid -eq 0 || $userid -ne `stat -c %u $DATADIR/PG_VERSION` ]]; then
    echo "This script must be executed as the unprivileged user who owns the postgres data directory (for example user postgres)"
    exit 1
fi

# Get PostgreSQL PID if pidfile exists
PGSQLPID=0
if [ -f "$PIDFILE" ]; then
  pid=`head -1 "$PIDFILE"`
  if [ $pid -gt 0 ]; then
    PGSQLPID=$pid
  else
    PGSQLPID=0
  fi
fi

is_pid_valid() {
  local returnval
  local procfile
  local grepstr
  local linecount
  local pid
  returnval=1
  
  if [ $PGSQLPID -gt 0 ]; then
    # Look into the process commandline to check if it is PostgreSQL and with correct pidfile
    procfile=/proc/$PGSQLPID/cmdline
    if [ -f "$procfile" ]; then
      grepstr=$(printf "%s\n%s\n" "postgres" "$DATADIR")
      linecount=`strings $procfile | grep -F "$grepstr" | wc -l`
      if [ $linecount -ge 2 ]; then
        returnval=0
      fi
    fi
  fi

  return $returnval
}

ping_instance() {
  $SOFTWARE/bin/psql --host "$SOCKET" -c "SELECT 1" &> /dev/null
  if [ $? -eq 0 ]; then
    return 0
  else
    return 1
  fi
}

start() {
  read_info_from_lockfile
  # Issue a warning, but assume clusterware deals with that situation
  if [[ -n "$LOCK_HOSTNAME" && "$HOSTNAME" != "$LOCK_HOSTNAME" ]]; then
    writelog "Found leftover lockfile from host $LOCK_HOSTNAME..." w
    if [[ -n "$_CRS_NAME" ]]; then
      if [[ `stat -c %Z "$LOCKFILE"` -ge `date --date="40 second ago" +%s` ]]; then
        writelog "CRS_ERROR Lockfile updated too recently" e
        exit 1
      else
        writelog "Continuing to start up, clusterware should prevent double startups"
      fi
    else
      writelog "Since clusterware environment was not found, cancelling startup. If the same instance is not running on the other node, then remove $LOCKFILE before trying again." e
      exit 1
    fi
  fi
  
  # If local pidfile exists, check if it is indeed PostgreSQL and then issue kill
  if is_pid_valid; then
    writelog "PostgreSQL already running..." w
    stop
  fi

  writelog "Starting PostgreSQL"
  $SOFTWARE/bin/postgres -D "$DATADIR" -h "$BINDADDR" -k "$SOCKET" -d 1 2>&1 | logger -t "$SYSLOGTAG" &

  # Get PID
  # Write lockfile
  echo "$HOSTNAME" > $LOCKFILE
  jobs -p >> $LOCKFILE
  # If times out, then starting is successful, and check should return PARTIAL status
  exitval=0
  # Wait for PID file
  for i in {1..10}; do
    if [ -f "$PIDFILE" ]; then
      # Try connecting
      ping_instance && disown %+ && break
    fi
    # Check if bash job still exists
    jobs %+ &> /dev/null
    if [[ $? -eq 0 ]]; then
      writelog "Waiting to start"
      sleep 3
    else
      writelog "Error starting PostgreSQL" e
#      grep "ERROR" $LOGFILE | tail -5
      rm -f $LOCKFILE
      exitval=1
      break
    fi
  done
}

stop() {
  # Send kill signal to PostgreSQL process
  if [ $PGSQLPID -gt 0 ]; then
    if is_pid_valid; then
      writelog "Stopping PostgreSQL"
      kill -s "$STOPSIGNAL" "$PGSQLPID" &> /dev/null
      # Try to wait a little, until postgres has exited
      for i in {1..20}; do
        kill -0 "$PGSQLPID" &> /dev/null || break
        sleep 3
      done
      kill -0 "$PGSQLPID" &> /dev/null
      if [[ $? -eq 0 ]]; then
        writelog "postgres is still stopping, exiting in failed state" w
        exitval=1
      else
        rm -f "$LOCKFILE"
        exitval=0
      fi
    else
      writelog "PID not valid, exiting in failed state" e
      exitval=1
    fi
  fi
}

clean() {
  read_info_from_lockfile
  # If still alive, send KILL
  if is_pid_valid; then
    writelog "Still alive, sending KILL..."
    kill -s SIGQUIT "$PGSQLPID"
    sleep 1
  fi
  # Try killing the PID in lockfile
  if [[ "$HOSTNAME" == "$LOCK_HOSTNAME" ]]; then
    PGSQLPID=$LOCK_PID
    if is_pid_valid; then
      writelog "Lockfile PID still alive, sending KILL..."
      kill -s SIGQUIT "$PGSQLPID"
      sleep 1
    fi
    rm -f "$LOCKFILE"
  fi
  # Remove pidfile
  if [ -f "$PIDFILE" ]; then
    rm -f "$PIDFILE"
  fi
}

check() {
  read_info_from_lockfile
  # If no pid, then fail immediately
  if ! is_pid_valid; then
    if [[ "$HOSTNAME" == "$LOCK_HOSTNAME" ]]; then
      # Lock file is created by the same host
      PGSQLPID=$LOCK_PID
      if is_pid_valid; then
        writelog "PostgreSQL process exists, but no PID" w
        exit 4
      else
        writelog "No pid, unplanned shutdown" w
        rm -f "$LOCKFILE"
        exit 1
      fi
    elif [[ -z "$LOCK_HOSTNAME" ]]; then
      # No lockfile
      writelog "No pid, planned shutdown"
      exit 2
    else
      # Lock file is from different host
      writelog "No pid, different host" w
      exit 1
    fi
  fi
  
  # if pid exists, but lockfile is from different host, then stop
  if [[ "$HOSTNAME" != "$LOCK_HOSTNAME" ]]; then
    writelog "Lock file is from different host, failing" e
    exit 5
  fi

  # Ping the instance
  if ping_instance; then
    # Renew lockfile timestamp
    touch $LOCKFILE
    exit 0
  else
    if [[ "$PGSQLPID" == "$LOCK_PID" ]]; then
      writelog "Database not accessible, but process is up, assuming PARTIAL"
      touch "$LOCKFILE"
      exit 4
    else
      # Issue kill command
      writelog "Server is not accessible, killing" e
      stop
      exit 1
    fi
  fi

}

console() {
  # Execute psql console
  $SOFTWARE/bin/psql -h "$SOCKET" <&0
  exit $?
}

case "$1" in
  start)
    start
;;
  stop)
    stop
;;
  restart)
    stop
    sleep 2
    start
;;
  check)
    check
;;
  clean)
    clean
;;
  *)
    echo $"Usage: $0 {start|stop|restart|check|clean}. All other arguments execute psql console."
    console
esac

exit $exitval
